using System;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Net;
using System.Xml;
using System.Security.Authentication;

namespace HabilisDesign.Iota
{
	public class TwitterGateway
	{
		#region Fields

		private static readonly string TimelineFriendsUrl = "http://twitter.com/statuses/friends_timeline.xml";
		private static readonly string StatusUpdateUrl = "http://twitter.com/statuses/update.xml";
		private readonly WebClient m_WebClient;
		private string m_Username;
		private string m_Password;

		private int m_TweetMaxCount = 140;

		#endregion

		#region Properties

		/// <summary>
		/// Gets the tweet max count.
		/// </summary>
		/// <value>The tweet max count.</value>
		public int TweetMaxCount
		{
			get
			{
				return m_TweetMaxCount;
			}
		}

		/// <summary>
		/// Gets or sets the username.
		/// </summary>
		/// <value>The username.</value>
		public string Username
		{
			get
			{
				return m_Username;
			}
			set
			{
				if ( m_Username == value )
					return;
				m_Username = value;
			}
		}

		/// <summary>
		/// Gets or sets the password.
		/// </summary>
		/// <value>The password.</value>
		public string Password
		{
			get
			{
				return m_Password;
			}
			set
			{
				if ( m_Password == value )
					return;
				m_Password = value;
			}
		}

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="TwitterGateway"/> class.
		/// </summary>
		public TwitterGateway()
			: this( new WebClient(), string.Empty, string.Empty )
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="TwitterGateway"/> class.
		/// </summary>
		/// <param name="username">The username.</param>
		/// <param name="password">The password.</param>
		public TwitterGateway( string username, string password )
			: this( new WebClient(), username, password )
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="TwitterGateway"/> class.
		/// </summary>
		/// <param name="webClient">The web client.</param>
		/// <param name="username">The username.</param>
		/// <param name="password">The password.</param>
		public TwitterGateway( WebClient webClient, string username, string password )
		{
			m_WebClient = webClient;
			m_Username = username;
			m_Password = password;
		}

		#endregion

		#region Methods

		/// <summary>
		/// Converts to utc.
		/// </summary>
		/// <param name="localTime">The local time.</param>
		/// <returns></returns>
		private DateTime ConvertToUtc( DateTime localTime )
		{
			return localTime.ToUniversalTime();
		}

		/// <summary>
		/// Initializes the collection.
		/// </summary>
		/// <param name="collection">The collection.</param>
		public void InitializeCollection( StatusCollection collection )
		{
			string updates = GetStatusUpdates( collection.LastUpdated );
			if ( string.IsNullOrEmpty( updates ) )
			{
				collection.LastUpdated = DateTime.Now;
			}
			else
			{
				UpdateCollection( collection, updates, false );
			}
		}

		/// <summary>
		/// Updates the specified collection.
		/// </summary>
		/// <param name="collection">The collection.</param>
		public void Update( StatusCollection collection )
		{
			string updates = GetStatusUpdates( collection.LastUpdated );
			if ( string.IsNullOrEmpty( updates ) )
			{
				collection.LastUpdated = DateTime.Now;
			}
			else
			{
				UpdateCollection( collection, updates, true );
			}
		}

		/// <summary>
		/// Updates the collection.
		/// </summary>
		/// <param name="collection">The collection.</param>
		/// <param name="updates">The updates.</param>
		/// <param name="addToTop">if set to <c>true</c> [add to top].</param>
		public void UpdateCollection( StatusCollection collection, string updates, bool addToTop )
		{
			XmlReaderSettings settings = new XmlReaderSettings();
			settings.IgnoreProcessingInstructions = true;
			settings.IgnoreWhitespace = true;
			settings.IgnoreComments = true;
			settings.ConformanceLevel = ConformanceLevel.Fragment;
			settings.CloseInput = true;

			using ( XmlReader reader = XmlReader.Create( new StringReader( updates ), settings ) )
			{
				try
				{
					while ( !reader.EOF )
					{
						if ( reader.IsStartElement( "status" ) )
						{
							if ( addToTop )
							{
								collection.Insert( Status.Parse( reader.ReadInnerXml() ) );
							}
							else
							{
								collection.Add( Status.Parse( reader.ReadInnerXml() ) );
							}
						}
						else
						{
							reader.Read();
						}
					}
				}
				catch ( XmlException ex )
				{
					if ( !ex.Message.StartsWith( "Unexpected DTD declaration" ) )
                    {
						throw;
                    }
				}
			}
		}

		private void CheckAuthentication()
		{
			// Authentication is needed.
			if ( !string.IsNullOrEmpty( m_Username ) && !string.IsNullOrEmpty( m_Password ) )
			{
				m_WebClient.Credentials = new NetworkCredential( m_Username, m_Password );
			}
		}

		/// <summary>
		/// Gets the status updates.
		/// </summary>
		/// <param name="lastUpdated">The last updated.</param>
		/// <returns></returns>
		public string GetStatusUpdates( DateTime lastUpdated )
		{
			lock ( m_WebClient )
			{
				if ( lastUpdated != DateTime.MinValue )
				{
					string timeStamp = ConvertToUtc( lastUpdated ).ToString( "ddd dd MMM yyyy HH:mm:ss", CultureInfo.InvariantCulture );
					m_WebClient.QueryString.Add( "since", timeStamp );
				}

				CheckAuthentication();

				try
				{
					return m_WebClient.DownloadString( TimelineFriendsUrl );
				}
				catch ( WebException ex )
				{
					switch ( ex.Status )
					{
						case WebExceptionStatus.Timeout:
						case WebExceptionStatus.ConnectionClosed:
							{
								// Should this really just "ignore" the problems??
								break;
							}
						case WebExceptionStatus.ProtocolError:
							{
								switch ( ex.Response.Headers["Status"] )
								{
									case "304 Not Modified":
										{
											break;
										}
									case "401 Unauthorized":
										{
											throw new AuthenticationException( "Username or password is not valid." );
										}
									default:
										{
											throw;
										}
								}
								break;
							}
						default:
							{
								throw;
							}
					}
				}
				return string.Empty;
			}
		}

		/// <summary>
		/// Sends the status update.
		/// </summary>
		/// <param name="statusMessage">The status message.</param>
		public void SendStatusUpdate( string statusMessage )
		{
			lock ( m_WebClient )
			{
				CheckAuthentication();

				NameValueCollection postValues = new NameValueCollection();
				postValues.Add( "status", EncodeText( statusMessage ) );
				byte[] response = m_WebClient.UploadValues( StatusUpdateUrl, "POST", postValues );
			}
		}

		/// <summary>
		/// Encodes the text.
		/// </summary>
		/// <param name="message">The message.</param>
		/// <returns></returns>
		public string EncodeText( string message )
		{
			return message;
		}

		/// <summary>
		/// Decodes the text.
		/// </summary>
		/// <param name="message">The message.</param>
		/// <returns></returns>
		public string DecodeText( string message )
		{
			return message;
		}

		#endregion

	} // end class

} // end namespace
